package org.activiti.rest;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import junit.framework.AssertionFailedError;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.FormService;
import org.activiti.engine.HistoryService;
import org.activiti.engine.IdentityService;
import org.activiti.engine.ManagementService;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.identity.Group;
import org.activiti.engine.identity.User;
import org.activiti.engine.impl.ProcessEngineImpl;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.activiti.engine.impl.db.DbSqlSession;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.interceptor.CommandExecutor;
import org.activiti.engine.impl.jobexecutor.JobExecutor;
import org.activiti.engine.impl.test.PvmTestCase;
import org.activiti.engine.impl.test.TestHelper;
import org.activiti.engine.impl.util.ClockUtil;
import org.activiti.engine.impl.util.LogUtil.ThreadLogMode;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.rest.application.ActivitiRestApplication;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.Assert;
import org.restlet.Component;
import org.restlet.data.ChallengeScheme;
import org.restlet.data.Protocol;
import org.restlet.resource.ClientResource;
public class BaseRestTestCase extends PvmTestCase {
private static Logger log = Logger.getLogger(BaseRestTestCase.class.getName());
protected Component component;
protected ObjectMapper objectMapper = new ObjectMapper();
private static final List<String> TABLENAMES_EXCLUDED_FROM_DB_CLEAN_CHECK = Arrays.asList(
"ACT_GE_PROPERTY"
);
protected ProcessEngine processEngine;
protected static ProcessEngine cachedProcessEngine;
protected ThreadLogMode threadRenderingMode = DEFAULT_THREAD_LOG_MODE;
protected String deploymentId;
protected Throwable exception;
protected ProcessEngineConfigurationImpl processEngineConfiguration;
protected RepositoryService repositoryService;
protected RuntimeService runtimeService;
protected TaskService taskService;
protected FormService formService;
protected HistoryService historyService;
protected IdentityService identityService;
protected ManagementService managementService;
protected ClientResource getAuthenticatedClient(String uri) {
ClientResource client = new ClientResource("http://localhost:8182/" + uri);
client.setChallengeResponse(ChallengeScheme.HTTP_BASIC, "kermit", "kermit");
return client;
}
protected void initializeProcessEngine() {
if (cachedProcessEngine==null) {
cachedProcessEngine = ProcessEngineConfiguration.createStandaloneInMemProcessEngineConfiguration().setProcessEngineName("default").buildProcessEngine();
if (cachedProcessEngine==null) {
throw new ActivitiException("no in-memory process engine available");
}
// hack to circumvent the loading of the activiti-context.xml of the REST web application
ProcessEnginesRest.init();
ProcessEngines.registerProcessEngine(cachedProcessEngine);
}
processEngine = cachedProcessEngine;
}
@Override
public void runBare() throws Throwable {
initializeRestServer();
initializeProcessEngine();
if (repositoryService==null) {
initializeServices();
}
createUsers();
log.severe(EMPTY_LINE);
try {
deploymentId = TestHelper.annotationDeploymentSetUp(processEngine, getClass(), getName());
super.runBare();
} catch (AssertionFailedError e) {
log.severe(EMPTY_LINE);
log.log(Level.SEVERE, "ASSERTION FAILED: "+e, e);
exception = e;
throw e;
} catch (Throwable e) {
log.severe(EMPTY_LINE);
log.log(Level.SEVERE, "EXCEPTION: "+e, e);
exception = e;
throw e;
} finally {
TestHelper.annotationDeploymentTearDown(processEngine, deploymentId, getClass(), getName());
dropUsers();
assertAndEnsureCleanDb();
stopRestServer();
ClockUtil.reset();
}
}
protected void createUsers() {
IdentityService identityService = processEngine.getIdentityService();
User user = identityService.newUser("kermit");
user.setFirstName("Kermit");
user.setLastName("the Frog");
user.setPassword("kermit");
identityService.saveUser(user);
Group group = identityService.newGroup("admin");
group.setName("Administrators");
identityService.saveGroup(group);
identityService.createMembership(user.getId(), group.getId());
}
protected void initializeRestServer() throws Exception {
component = new Component();
// Add a new HTTP server listening on port 8182.
component.getServers().add(Protocol.HTTP, 8182);
component.getDefaultHost().attach(new ActivitiRestApplication());
component.start();
}
protected void dropUsers() {
IdentityService identityService = processEngine.getIdentityService();
identityService.deleteUser("kermit");
identityService.deleteGroup("admin");
identityService.deleteMembership("kermit", "admin");
}
protected void stopRestServer() throws Exception {
component.stop();
}
/** Each test is assumed to clean up all DB content it entered.
* After a test method executed, this method scans all tables to see if the DB is completely clean.
* It throws AssertionFailed in case the DB is not clean.
* If the DB is not clean, it is cleaned by performing a create a drop. */
protected void assertAndEnsureCleanDb() throws Throwable {
log.fine("verifying that db is clean after test");
Map<String, Long> tableCounts = managementService.getTableCount();
StringBuilder outputMessage = new StringBuilder();
for (String tableName : tableCounts.keySet()) {
String tableNameWithoutPrefix = tableName.replace(processEngineConfiguration.getDatabaseTablePrefix(), "");
if (!TABLENAMES_EXCLUDED_FROM_DB_CLEAN_CHECK.contains(tableNameWithoutPrefix)) {
Long count = tableCounts.get(tableName);
if (count!=0L) {
outputMessage.append(" "+tableName + ": " + count + " record(s) ");
}
}
}
if (outputMessage.length() > 0) {
outputMessage.insert(0, "DB NOT CLEAN: \n");
log.severe(EMPTY_LINE);
log.severe(outputMessage.toString());
log.info("dropping and recreating db");
CommandExecutor commandExecutor = ((ProcessEngineImpl)processEngine).getProcessEngineConfiguration().getCommandExecutorTxRequired();
commandExecutor.execute(new Command<Object>() {
public Object execute(CommandContext commandContext) {
DbSqlSession session = commandContext.getSession(DbSqlSession.class);
session.dbSchemaDrop();
session.dbSchemaCreate();
return null;
}
});
if (exception!=null) {
throw exception;
} else {
Assert.fail(outputMessage.toString());
}
} else {
log.info("database was clean");
}
}
protected void initializeServices() {
processEngineConfiguration = ((ProcessEngineImpl) processEngine).getProcessEngineConfiguration();
repositoryService = processEngine.getRepositoryService();
runtimeService = processEngine.getRuntimeService();
taskService = processEngine.getTaskService();
formService = processEngine.getFormService();
historyService = processEngine.getHistoryService();
identityService = processEngine.getIdentityService();
managementService = processEngine.getManagementService();
}
public void assertProcessEnded(final String processInstanceId) {
ProcessInstance processInstance = processEngine
.getRuntimeService()
.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
if (processInstance!=null) {
throw new AssertionFailedError("Expected finished process instance '"+processInstanceId+"' but it was still in the db");
}
}
public void waitForJobExecutorToProcessAllJobs(long maxMillisToWait, long intervalMillis) {
JobExecutor jobExecutor = processEngineConfiguration.getJobExecutor();
jobExecutor.start();
try {
Timer timer = new Timer();
InteruptTask task = new InteruptTask(Thread.currentThread());
timer.schedule(task, maxMillisToWait);
boolean areJobsAvailable = true;
try {
while (areJobsAvailable && !task.isTimeLimitExceeded()) {
Thread.sleep(intervalMillis);
areJobsAvailable = areJobsAvailable();
}
} catch (InterruptedException e) {
} finally {
timer.cancel();
}
if (areJobsAvailable) {
throw new ActivitiException("time limit of " + maxMillisToWait + " was exceeded");
}
} finally {
jobExecutor.shutdown();
}
}
public void waitForJobExecutorOnCondition(long maxMillisToWait, long intervalMillis, Callable<Boolean> condition) {
JobExecutor jobExecutor = processEngineConfiguration.getJobExecutor();
jobExecutor.start();
try {
Timer timer = new Timer();
InteruptTask task = new InteruptTask(Thread.currentThread());
timer.schedule(task, maxMillisToWait);
boolean conditionIsViolated = true;
try {
while (conditionIsViolated) {
Thread.sleep(intervalMillis);
conditionIsViolated = !condition.call();
}
} catch (InterruptedException e) {
} catch (Exception e) {
throw new ActivitiException("Exception while waiting on condition: "+e.getMessage(), e);
} finally {
timer.cancel();
}
if (conditionIsViolated) {
throw new ActivitiException("time limit of " + maxMillisToWait + " was exceeded");
}
} finally {
jobExecutor.shutdown();
}
}
public boolean areJobsAvailable() {
return !managementService
.createJobQuery()
.executable()
.list()
.isEmpty();
}
private static class InteruptTask extends TimerTask {
protected boolean timeLimitExceeded = false;
protected Thread thread;
public InteruptTask(Thread thread) {
this.thread = thread;
}
public boolean isTimeLimitExceeded() {
return timeLimitExceeded;
}
public void run() {
timeLimitExceeded = true;
thread.interrupt();
}
}
}